package xyz.xtt.pay.service.impl;

import static xyz.xtt.pay.utils.AssertUtils.isFalse;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import cn.hutool.core.util.IdUtil;
import xyz.xtt.pay.constants.ErrorMessage;
import xyz.xtt.pay.entity.TblPayLimit;
import xyz.xtt.pay.exception.ChainException;
import xyz.xtt.pay.mapper.TblPayLimitMapper;
import xyz.xtt.pay.service.ITblPayLimitService;
import xyz.xtt.user.client.response.UserInfoResponse;

/**
 * <p>
 * 用户交易行为限制表 服务实现类
 * </p>
 *
 * @author dale
 * @since 2025-02-18
 */
@Service
@RefreshScope
public class TblPayLimitServiceImpl extends ServiceImpl<TblPayLimitMapper, TblPayLimit> implements ITblPayLimitService {
	@Value("${buy.total.limit}")
	private Integer buyTotalLimit;
	@Value("${sell.total.limit}")
	private Integer sellTotalLimit;
	@Value("${buy.daily.limit}")
	private Integer buyDailyLimit;
	@Value("${sell.daily.limit}")
	private Integer sellDailyLimit;
	@Value("${daily.spend.face.auth.threshold}")
	private Integer dailySpendFaceAuthThreshold;

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void preUseAndCheckPayLimit(UserInfoResponse buyUser, Long sellUserId, BigDecimal realTotalPrice) {
		long buyUserId = buyUser.getAppUserId();
		Map<Long, TblPayLimit> payLimitMap = this.getLatestLimitByUserIds(Arrays.asList(buyUserId, sellUserId));
		TblPayLimit buyerLimit = payLimitMap.get(buyUserId);
		TblPayLimit sellerLimit = payLimitMap.get(sellUserId);

		if (buyerLimit.getBuyDaily().add(realTotalPrice).compareTo(new BigDecimal(dailySpendFaceAuthThreshold)) >= 0 && !buyUserHasFaceAuthToday(buyUser)) {
			throw new ChainException(ErrorMessage.BUY_SPEND_FACE_AUTH_THRESHOLD_ARRIVED);
		} else if (buyerLimit.getBuyDaily().add(realTotalPrice).compareTo(buyerLimit.getBuyDailyLimit()) > 0) {
			throw new ChainException(ErrorMessage.BUY_DAILY_LIMIT_EX);
		} else if (buyerLimit.getBuyTotal().add(realTotalPrice).compareTo(buyerLimit.getBuyTotalLimit()) > 0) {
			throw new ChainException(ErrorMessage.BUY_TOTAL_LIMIT_EX);
		} else if (sellerLimit.getSellDaily().add(realTotalPrice).compareTo(sellerLimit.getSellDaliyLimit()) > 0) {
			throw new ChainException(ErrorMessage.SELL_DAILY_LIMIT_EX);
		} else if (sellerLimit.getSellTotal().add(realTotalPrice).compareTo(sellerLimit.getSellTotalLimit()) > 0) {
			throw new ChainException(ErrorMessage.SELL_TOTAL_LIMIT_EX);
		}

		// 更新数据
		LocalDateTime now = LocalDateTime.now();
		LambdaUpdateChainWrapper<TblPayLimit> update = new LambdaUpdateChainWrapper<>(baseMapper);
		isFalse(update
				.eq(TblPayLimit::getPayLimitId, buyerLimit.getPayLimitId())
				.eq(TblPayLimit::getDataVersion, buyerLimit.getDataVersion())
				.set(TblPayLimit::getBuyDaily, buyerLimit.getBuyDaily().add(realTotalPrice))
				.set(TblPayLimit::getBuyTotal, buyerLimit.getBuyTotal().add(realTotalPrice))
				.set(TblPayLimit::getUpdateTime, now)
				.set(TblPayLimit::getDataVersion, buyerLimit.getDataVersion() + 1)
				.update(), ErrorMessage.ORDER_CREATE_FAILED, () -> log
						.error("preUseAndCheckPayLimit update buyer failed payLimitId: " + buyerLimit.getPayLimitId()));

		update = new LambdaUpdateChainWrapper<>(baseMapper);
		isFalse(update
				.eq(TblPayLimit::getPayLimitId, sellerLimit.getPayLimitId())
				.eq(TblPayLimit::getDataVersion, sellerLimit.getDataVersion())
				.set(TblPayLimit::getSellDaily, sellerLimit.getSellDaily().add(realTotalPrice))
				.set(TblPayLimit::getSellTotal, sellerLimit.getSellTotal().add(realTotalPrice))
				.set(TblPayLimit::getUpdateTime, now)
				.set(TblPayLimit::getDataVersion, sellerLimit.getDataVersion() + 1)
				.update(), ErrorMessage.ORDER_CREATE_FAILED, () -> log
						.error("preUseAndCheckPayLimit update seller failed payLimitId: " + sellerLimit.getPayLimitId()));
	}

	private boolean buyUserHasFaceAuthToday(UserInfoResponse buyUser) {
		// 当天0点0分0秒时间戳
		long todayStartTimestamp = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
		return buyUser.getAuthBefore() != null && buyUser.getAuthBefore() > todayStartTimestamp;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void cancelUserPayLimit(Long buyUserId, Long sellUserId, BigDecimal canceledMoney, Long createTime) {
		// 当天0点0分0秒时间戳
		long todayStartTimestamp = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();

		Map<Long, TblPayLimit> payLimitMap = this.getLatestLimitByUserIds(Arrays.asList(buyUserId, sellUserId));
		TblPayLimit buyerLimit = payLimitMap.get(buyUserId);
		TblPayLimit sellerLimit = payLimitMap.get(sellUserId);

		LambdaUpdateChainWrapper<TblPayLimit> buyerUpdateWrapper = new LambdaUpdateChainWrapper<>(baseMapper);
		buyerUpdateWrapper
				.eq(TblPayLimit::getPayLimitId, buyerLimit.getPayLimitId())
				.eq(TblPayLimit::getDataVersion, buyerLimit.getDataVersion())
				.set(TblPayLimit::getBuyTotal, buyerLimit.getBuyTotal().subtract(canceledMoney))
				.set(TblPayLimit::getDataVersion, buyerLimit.getDataVersion() + 1);
		LambdaUpdateChainWrapper<TblPayLimit> sellerUpdateWrapper = new LambdaUpdateChainWrapper<>(baseMapper);
		sellerUpdateWrapper
				.eq(TblPayLimit::getPayLimitId, sellerLimit.getPayLimitId())
				.eq(TblPayLimit::getDataVersion, sellerLimit.getDataVersion())
				.set(TblPayLimit::getSellTotal, sellerLimit.getSellTotal().subtract(canceledMoney))
				.set(TblPayLimit::getDataVersion, sellerLimit.getDataVersion() + 1);
		// 更新数据
		if (createTime >= todayStartTimestamp && createTime < todayStartTimestamp + 24 * 60 * 60 * 1000) {
			buyerUpdateWrapper.set(TblPayLimit::getBuyDaily, buyerLimit.getBuyDaily().subtract(canceledMoney));
			sellerUpdateWrapper.set(TblPayLimit::getSellDaily, sellerLimit.getSellDaily().subtract(canceledMoney));
		}
		isFalse(buyerUpdateWrapper.update(), ErrorMessage.RELEASE_PAY_LIMIT_FAILED, () -> log
				.error("cancelUserPayLimit update buyer failed payLimitId: " + buyerLimit.getPayLimitId()));
		isFalse(sellerUpdateWrapper.update(), ErrorMessage.RELEASE_PAY_LIMIT_FAILED, () -> log
				.error("cancelUserPayLimit update seller failed payLimitId: " + sellerLimit.getPayLimitId()));
	}

	private Map<Long, TblPayLimit> getLatestLimitByUserIds(List<? extends Long> list) {
		if (CollectionUtils.isEmpty(list)) {
			return Map.of();
		}
		// 当天0点0分0秒时间戳
		long todayStartTimestamp = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();

		List<TblPayLimit> limitList = baseMapper.findLatestByUserIds(list);
		Map<Long, TblPayLimit> map;
		if (CollectionUtils.isEmpty(limitList)) {
			map = new HashMap<>();
		} else {
			map = limitList.stream().collect(Collectors.toMap(TblPayLimit::getAppUserId, Function.identity()));
		}

		// 对未设置限额的用户进行初始化
		LocalDateTime now = LocalDateTime.now();
		List<TblPayLimit> needInserts = new ArrayList<>();
		for (Long appUserId : list) {
			if (!map.containsKey(appUserId)) {
				// 查询最近日期数据
				TblPayLimit tblPayLimit = TblPayLimit
						.builder()
						.payLimitId(IdUtil.getSnowflakeNextId())
						.appUserId(appUserId)
						.buyTotalLimit(new BigDecimal(buyTotalLimit))
						.sellTotalLimit(new BigDecimal(sellTotalLimit))
						.buyDailyLimit(new BigDecimal(buyDailyLimit))
						.sellDaliyLimit(new BigDecimal(sellDailyLimit))
						.buyTotal(BigDecimal.ZERO)
						.buyDaily(BigDecimal.ZERO)
						.sellTotal(BigDecimal.ZERO)
						.sellDaily(BigDecimal.ZERO)
						.dailyDate(todayStartTimestamp)
						.dataVersion(0)
						.operatorUserId("SYSTEM")
						.createTime(now)
						.updateTime(now)
						.isDel(0)
						.build();
				needInserts.add(tblPayLimit);
				map.put(appUserId, tblPayLimit);
				continue;
			}
			if (map.get(appUserId).getDailyDate() < todayStartTimestamp) {
				// 查询最近日期数据
				TblPayLimit tblPayLimit = TblPayLimit
						.builder()
						.payLimitId(IdUtil.getSnowflakeNextId())
						.appUserId(appUserId)
						.dailyDate(todayStartTimestamp)
						.buyTotalLimit(map.get(appUserId).getBuyTotalLimit())
						.sellTotalLimit(map.get(appUserId).getSellTotalLimit())
						.buyDailyLimit(map.get(appUserId).getBuyDailyLimit())
						.sellDaliyLimit(map.get(appUserId).getSellDaliyLimit())
						.buyTotal(map.get(appUserId).getBuyTotal())
						.sellTotal(map.get(appUserId).getSellTotal())
						.buyDaily(BigDecimal.ZERO)
						.sellDaily(BigDecimal.ZERO)
						.operatorUserId("SYSTEM")
						.dataVersion(0)
						.createTime(now)
						.updateTime(now)
						.isDel(0)
						.build();
				needInserts.add(tblPayLimit);
				map.put(appUserId, tblPayLimit);
			}
		}
		if (!CollectionUtils.isEmpty(needInserts)) {
			saveBatch(needInserts);
		}
		return map;
	}

}
